package dev.ece.suit.loader.models.dae;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.tools.Constants;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="library_geometries")
public class DAEGeometries {

	@XmlElement
	private Geometry[] geometry;
	
	public Geometry getGeometry(String id) {
		if(geometry != null) {
			for(Geometry geometry: this.geometry) {
				if(StringUtils.equals(id, StringUtils.join("#", geometry.getId()))) {
					return geometry;
				}
			}
		}
		return null;
	}
	
	public Geometry[] getGeometry() {
		return geometry;
	}

	public void setGeometry(Geometry[] geometry) {
		this.geometry = geometry;
	}

	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="geometry")
	public static class Geometry {
		
		@XmlAttribute
		private String id;
		
		@XmlAttribute
		private String name;
		
		@XmlElement
		private Mesh mesh;

		public String getId() {
			return id;
		}

		public void setId(String id) {
			this.id = id;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public Mesh getMesh() {
			return mesh;
		}

		public void setMesh(Mesh mesh) {
			this.mesh = mesh;
		}
		
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="mesh")
	public static class Mesh {
		
		@XmlElement
		private Source[] source;
		
		@XmlElement
		private Vertices vertices;
		
		@XmlElement
		private Triangles[] triangles;
		
		public Map<String, Source> toSourceMap() {
			Map<String, Source> map = new ConcurrentHashMap<String, Source>();
			if(source != null) {
				for(Source source: this.source) {
					map.put(StringUtils.join("#", source.getId()), source);
				}
			}
			this.vertices.fillSourceMap(map);
			return map;
		}

		public Source[] getSource() {
			return source;
		}

		public void setSource(Source[] source) {
			this.source = source;
		}

		public Vertices getVertices() {
			return vertices;
		}

		public void setVertices(Vertices vertices) {
			this.vertices = vertices;
		}

		public Triangles[] getTriangles() {
			return triangles;
		}

		public void setTriangles(Triangles[] triangles) {
			this.triangles = triangles;
		}
		
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="triangles")
	public static class Triangles {
		
		@XmlAttribute
		private int count;
		
		@XmlAttribute
		private String material;
		
		@XmlElement
		private Input[] input;
		
		@XmlElement
		private String p;

		public int getCount() {
			return count;
		}

		public void setCount(int count) {
			this.count = count;
		}

		public String getMaterial() {
			return material;
		}

		public void setMaterial(String material) {
			this.material = material;
		}

		public Input[] getInput() {
			return input;
		}

		public void setInput(Input[] input) {
			this.input = input;
		}

		public String getP() {
			return p;
		}

		public void setP(String p) {
			this.p = p;
		}
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="vertices")
	public static class Vertices {
		@XmlAttribute
		private String id;
		
		@XmlElement
		private Input input;

		public void fillSourceMap(Map<String, Source> sources) {
			if(input != null) {
				sources.put(StringUtils.join("#", this.getId()), sources.get(input.getSource()));
			}
		}
		
		public String getId() {
			return id;
		}

		public void setId(String id) {
			this.id = id;
		}

		public Input getInput() {
			return input;
		}

		public void setInput(Input input) {
			this.input = input;
		}
		
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="input")
	public static class Input {
	
		@XmlAttribute
		private String semantic;
		
		@XmlAttribute
		private String source;
		
		@XmlAttribute
		private int offset;

		public String getSemantic() {
			return semantic;
		}

		public void setSemantic(String semantic) {
			this.semantic = semantic;
		}

		public String getSource() {
			return source;
		}

		public void setSource(String source) {
			this.source = source;
		}

		public int getOffset() {
			return offset;
		}

		public void setOffset(int offset) {
			this.offset = offset;
		}
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="source")
	public static class Source {
		
		@XmlAttribute
		private String id;
		
		@XmlElement
		private Array float_array;
		
		@XmlElement(name="Name_array")
		private Array name_array;
		
		@XmlElement
		private TechniqueCommon technique_common;

		public float[] toFloatArray() throws IOException {
			String[] array = StringUtils.split(StringUtils.trim(Constants.inline(float_array.getContent()).toString()), " ");
			float[] result = new float[array.length];
			for(int i=0; i<array.length; i++) {
				result[i] = NumberUtils.toFloat(array[i]);
			}
			return result;
		}
		
		public Matrix4f[] toMatrix4fArray() throws IOException {
			String[] array = StringUtils.split(StringUtils.trim(Constants.inline(float_array.getContent()).toString()), " ");
			float[] farray = null;
			Matrix4f[] matrixs = new Matrix4f[this.technique_common.getAccessor().getCount()];
			int index = 0;
			for(int i=0; i<matrixs.length; i++) {
				farray = new float[this.technique_common.getAccessor().getStride()];
				for(int j=0; j<4; j++) {
					farray[j] = NumberUtils.toFloat(array[index ++]);
					farray[4 + j] = NumberUtils.toFloat(array[index ++]);
					farray[8 + j] = NumberUtils.toFloat(array[index ++]);
					farray[12 + j] = NumberUtils.toFloat(array[index ++]);
				}
				matrixs[i] = new Matrix4f(farray);
			}
			return matrixs;
		}
		
		public String[] toStringArray() throws IOException {
			return StringUtils.split(StringUtils.trim(Constants.inline(name_array.getContent()).toString()), " ");
		}
		
		public Vector3f[] toVector3f() throws IOException {
			int count = technique_common.getAccessor().getCount();
			Vector3f[] result = new Vector3f[count];
			String[] array = StringUtils.split(StringUtils.trim(Constants.inline(this.float_array.content).toString()), " ");
			int index = 0;
			for(int i=0; i<count; i++) {
				result[i]= new Vector3f(
						NumberUtils.toFloat(array[index ++]), 
						NumberUtils.toFloat(array[index ++]), 
						NumberUtils.toFloat(array[index ++]));
			}
			return result;
		}
		
		public Vector3f[] toVector3f(Matrix4f matrix) throws IOException {
			int count = technique_common.getAccessor().getCount();
			Vector3f[] result = new Vector3f[count];
			String[] array = StringUtils.split(StringUtils.trim(Constants.inline(this.float_array.content).toString()), " ");
			int index = 0;
			for(int i=0; i<count; i++) {
				result[i]= matrix.transform(new Vector3f(
						NumberUtils.toFloat(array[index ++]), 
						NumberUtils.toFloat(array[index ++]), 
						NumberUtils.toFloat(array[index ++])));
			}
			return result;
		}
		
		public Vector2f[] toVector2f() throws IOException {
			int count = technique_common.getAccessor().getCount();
			Vector2f[] result = new Vector2f[count];
			String[] array = StringUtils.split(StringUtils.trim(Constants.inline(this.float_array.content).toString()), " ");
			int index = 0;
			for(int i=0; i<count; i++) {
				result[i] = new Vector2f(
						NumberUtils.toFloat(array[index ++]), 
						1.0f - NumberUtils.toFloat(array[index ++]));
			}
			return result;
		}
		
		public String getId() {
			return id;
		}

		public void setId(String id) {
			this.id = id;
		}

		public Array getFloat_array() {
			return float_array;
		}

		public void setFloat_array(Array float_array) {
			this.float_array = float_array;
		}

		public Array getName_array() {
			return name_array;
		}

		public void setName_array(Array name_array) {
			this.name_array = name_array;
		}

		public TechniqueCommon getTechnique_common() {
			return technique_common;
		}

		public void setTechnique_common(TechniqueCommon technique_common) {
			this.technique_common = technique_common;
		}
		
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="technique_common")
	public static class TechniqueCommon {
		
		@XmlElement
		private Accessor accessor;

		public Accessor getAccessor() {
			return accessor;
		}

		public void setAccessor(Accessor accessor) {
			this.accessor = accessor;
		}
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="accessor")
	public static class Accessor {
		
		@XmlAttribute
		private int count;
		
		@XmlAttribute
		private int stride;

		public int getCount() {
			return count;
		}

		public void setCount(int count) {
			this.count = count;
		}

		public int getStride() {
			return stride;
		}

		public void setStride(int stride) {
			this.stride = stride;
		}
	}
	
	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name="float_array")
	public static class Array {
		
		@XmlAttribute
		private String id;
		
		@XmlAttribute
		private int count;
		
		@XmlValue
		private String content;

		public String getId() {
			return id;
		}

		public void setId(String id) {
			this.id = id;
		}

		public int getCount() {
			return count;
		}

		public void setCount(int count) {
			this.count = count;
		}

		public String getContent() {
			return content;
		}

		public void setContent(String content) {
			this.content = content;
		}
		
	}
}
